// export tool project main.go
package main

import (
	"bufio"
	"encoding/csv"
	"flag"
	"fmt"
	"github.com/emirpasic/gods/lists/arraylist"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"syscall"
	"unicode"

	"github.com/emirpasic/gods/sets/hashset"

	"github.com/emirpasic/gods/maps/hashmap"
	"github.com/emirpasic/gods/maps/linkedhashmap"
	"github.com/tealeg/xlsx"

	"github.com/antlabs/strsim"
)

const (
	FlagOnly          = "[不可重复]"
	DecimalConfig     = "[小数配置]"
	SpacesConfig      = "[空格配置]"
	StringFlag        = "[字符串]"
	ListFlag          = "[List]"
	NoTranslationFlag = "[不要翻译]"
)

var (
	outPath        *string            //输出到指定目录
	tplOutputDir   string             //TPL输出目录
	pauseFlag      *bool              //当遇到错误是否暂停执行
	zone           *string            //地区编号
	allSheet       *hashmap.Map       //处理过的Sheet，key->sheetName,value->fileName
	errTexts       *hashset.Set       //错误的文本
	ignoreTpl      *hashset.Set       //忽略生成TPL
	ignoreExcel    *hashset.Set       //忽略解析的Excel
	i18nFlag       *bool              //是否开启I18N
	dict           map[string]string  //翻译好的字典
	allText        *linkedhashmap.Map //需要翻译的文本
	translatedText *linkedhashmap.Map //已翻译的文本（不包含翻译过已不使用的文本）
	step           *int               //阶服
	matchFlag      *bool              //相似度的显示
	textSourceMap  *hashmap.Map       //文本来源
	limit          *int               //Sheet名称长度限制
	comma          *int               //生成的CSV分隔符
	jsonFlag       *bool              //生成JSON
	csvFlag        *bool              //生成CSV
)

func main() {
	excelPath := flag.String("path", ".", "-path 指定CSV文件所在路径")
	outPath = flag.String("out", ".", "-out 输出到指定目录")
	pauseFlag = flag.Bool("pause", false, "-pause 当遇到错误是否暂停执行")
	i18nFlag = flag.Bool("i18n", false, "-i18n 是否开启国际化功能")
	matchFlag = flag.Bool("match", false, "-match 是否显示相似度的文本")
	zone = flag.String("zone", "CN", "-zone 指定地区编号")
	file := flag.String("file", "", "-file 需要单一导表的文件")
	step = flag.Int("step", 1, "-step 阶服配置,默认为1:不开启")
	limit = flag.Int("limit", 0, "-limit Sheet名称长度提醒，默认为0：不开启")
	comma = flag.Int("comma", '	', "-comma 生成的CSV分隔符，默认为Tab符")
	jsonFlag = flag.Bool("json", false, "-json 是否生成JSON格式的文件")
	csvFlag = flag.Bool("csv", true, "-csv 是否生成CSV格式的文件")
	flag.Parse()

	// 做一个特别的设定
	if strings.EqualFold("CN", *zone) {
		*i18nFlag = false
	}

	single := !strings.EqualFold("", *file) //标识当前是否为单一文件

	var files = make([]string, 0, 512)
	if single {
		files = append(files, *file)
		fmt.Printf("本次导表只处理单一配置文件\n")
	} else {
		files, _ = scanningExcelFiles(*excelPath) //扫描出所有Excel文件
		fmt.Printf("本次导表一共扫描到%d个配置文件\n", len(files))
	}

	tplOutputDir = *outPath + "/data/" + *zone + "/"
	// 开始阶服配置，那要加入阶服目录
	if *step > 1 {
		tplOutputDir = tplOutputDir + strconv.Itoa(*step) + "/"
	}
	if !single { //如果只导一个文件，那目标目录不能清理
		cleanUpDataDir(tplOutputDir, ".tpl") //清理将要生成的TPL目录
		if *jsonFlag {
			cleanUpDataDir(tplOutputDir+"json/", ".json") //清理将要生成的JSON目录
		}
	}

	allSheet = hashmap.New()
	allText = linkedhashmap.New()
	translatedText = linkedhashmap.New()
	textSourceMap = hashmap.New()
	errTexts = hashset.New()
	ignoreTpl = hashset.New()
	ignoreExcel = hashset.New()
	errTexts.Add("#N/A", "#REF!", "#NAME?")

	// 翻译好的字典
	if *i18nFlag {
		dict = i18nDict()
	}

	// 导表配置文件
	initConfig(*excelPath)

	// 遍历excel文件
	for _, fileName := range files {
		// 忽略不要解析的Excel，以便加速导表时间
		if ignoreExcel.Contains(filepath.Base(fileName)) {
			continue
		}

		//2003版本的给个提示，不作处理了
		if strings.HasSuffix(fileName, ".xls") {
			Error("\n\n警告：发现2003版本Excel配置[%s]，智能忽略...\n", fileName)
			if *pauseFlag {
				Error("确认忽略请按回车键继续执行...\n")
				input := bufio.NewScanner(os.Stdin)
				input.Scan()
			}
		}

		//处理Excel文件
		if !single || strings.HasSuffix(fileName, ".xlsx") || strings.HasSuffix(fileName, ".xlsm") {
			handleExcelFile(fileName)
		} else {
			Error("\n\n忽略非Excel文件[%s]\n", fileName)
		}
	}
	fmt.Println()

	if *i18nFlag {
		handleI18n(allText)
	}

	fmt.Println()
	fmt.Println("导出已完成")
	fmt.Println()
}

// 初始化配置文件
func initConfig(path string) {
	xlFile, err := xlsx.OpenFile(path + "/exporter.xlsx")
	if err == nil {
		for _, sheet := range xlFile.Sheets {
			// 忽略TPL
			if strings.EqualFold("忽略TPL", sheet.Name) {
				for _, row := range sheet.Rows {
					if len(row.Cells) == 0 { // 空行
						continue
					}

					tplFileName := row.Cells[0].String()
					if len(tplFileName) > 0 {
						ignoreTpl.Add(tplFileName)
					}
				}
			}
			// 忽略解析的Excel
			if strings.EqualFold("忽略Excel", sheet.Name) {
				for _, row := range sheet.Rows {
					if len(row.Cells) == 0 { // 空行
						continue
					}

					excelFileName := row.Cells[0].String()
					if len(excelFileName) > 0 {
						ignoreExcel.Add(excelFileName)
					}
				}
			}
		}
	}
}

//提取翻译好的字典
func i18nDict() map[string]string {
	dict := make(map[string]string)
	xlFile, err := xlsx.OpenFile(*outPath + "/i18n/" + *zone + ".xlsx")
	if err == nil {
		for _, sheet := range xlFile.Sheets {
			for lineNum, row := range sheet.Rows {
				if lineNum > 0 && len(row.Cells) >= 1 {
					textKey := row.Cells[0].String()
					if len(row.Cells) >= 2 && len(row.Cells[1].String()) > 0 {
						// 已翻译
						if _, ok := dict[textKey]; !ok {
							// 日本的空格给换掉
							text := row.Cells[1].String()
							text = strings.ReplaceAll(text, "　", " ")
							dict[textKey] = text
						}
					} else {
						// 没翻译
						allText.Put(textKey, "")
					}
				}
			}
		}
	}
	return dict
}

// 处理语言翻译的字典提取与合并
func handleI18n(allTextMap *linkedhashmap.Map) {
	xlsxFile := xlsx.NewFile()

	// 要翻译的
	{
		sheet, _ := xlsxFile.AddSheet("需要翻译的文字")
		row := sheet.AddRow()
		cell := row.AddCell()
		cell.SetString("原始文字")
		cell = row.AddCell()
		cell.SetString("翻译文字")

		array := make([]string, translatedText.Size())
		if *matchFlag {
			cell = row.AddCell()
			cell.SetString("相似原始文字")
			cell = row.AddCell()
			cell.SetString("相似翻译文字")

			for index, e := range translatedText.Keys() {
				array[index] = e.(string)
			}
			fmt.Println("正在搜索相似翻译内容，时间可能有些长，您可以先去喝杯咖啡...")
		}

		index := 0
		allTextMap.Each(func(key interface{}, value interface{}) {
			row := sheet.AddRow()
			cell := row.AddCell()
			cell.SetString(key.(string))

			// 查找相似翻译
			if *matchFlag {
				index++
				fmt.Print(index)
				fmt.Print("/")
				fmt.Println(allTextMap.Size())

				//去除所有尖括号内代码
				re, _ := regexp.Compile("\\<[\\S\\s]+?\\>")
				testStr := re.ReplaceAllString(key.(string), "")

				match := strsim.FindBestMatchOne(testStr, array)
				if match.Score > 0.8 {
					// 待翻译
					row.AddCell()

					// 相似源码
					cell = row.AddCell()
					cell.SetString(match.S)

					// 相似源码
					cell = row.AddCell()
					v, exist := translatedText.Get(match.S)
					if exist {
						cell.SetString(v.(string))
					}
				}
			}
		})
	}

	// 历史记录
	{
		sheet, _ := xlsxFile.AddSheet("已翻译好的文字")
		row := sheet.AddRow()
		cell := row.AddCell()
		cell.SetString("原始文字")
		cell = row.AddCell()
		cell.SetString("翻译文字")
		cell = row.AddCell()
		cell.SetString("文字来源")

		for key := range dict {
			_, exist := translatedText.Get(key)
			if !exist {
				translatedText.Put(key, dict[key])
			}
		}

		translatedText.Each(func(key interface{}, value interface{}) {
			row := sheet.AddRow()
			row.AddCell().SetString(key.(string))
			row.AddCell().SetString(value.(string))
			// 有来源
			tpl, found := textSourceMap.Get(key.(string))
			if found {
				row.AddCell().SetString(tpl.(string))
			}
		})
	}

	_ = xlsxFile.Save(*outPath + "/i18n/" + *zone + ".xlsx")
}

func Error(s string, a ...interface{}) {
	kernel32 := syscall.NewLazyDLL("kernel32.dll")
	proc := kernel32.NewProc("SetConsoleTextAttribute")
	handle, _, _ := proc.Call(uintptr(syscall.Stdout), uintptr(4|8)) //12 Red light

	fmt.Printf(s, a...)

	handle, _, _ = proc.Call(uintptr(syscall.Stdout), uintptr(7)) //White dark
	CloseHandle := kernel32.NewProc("CloseHandle")
	_, _, _ = CloseHandle.Call(handle)
}

// CheckNamingSpecification 检测标题行命令是否规范
func CheckNamingSpecification(list []string, sheetName string) {
	aLen := len(list)
	for i := 0; i < aLen; i++ {
		if strings.Contains(list[i], " ") {
			Error("\n\n警告：[%s]里发现标题行中包含空格命名字段[%s]，请火速修正...\n", sheetName, list[i])
			if *pauseFlag {
				Error("确认忽略请按回车键继续执行...\n")
				input := bufio.NewScanner(os.Stdin)
				input.Scan()
			}
		}
	}
}

// CheckDuplicateTitle 检测重复的标题
func CheckDuplicateTitle(list []string, sheetName string) {
	aLen := len(list)
	for i := 0; i < aLen-1; i++ {
		for x := i + 1; x < aLen; x++ {
			if list[i] == list[x] {
				Error("\n\n警告：[%s]里发现重复字段[%s]，请火速修正...\n", sheetName, list[i])
				if *pauseFlag {
					Error("确认忽略请按回车键继续执行...\n")
					input := bufio.NewScanner(os.Stdin)
					input.Scan()
				}
			}
		}
	}
}

func IsChineseChar(str string) bool {
	for _, r := range str {
		if unicode.Is(unicode.Scripts["Han"], r) || (regexp.MustCompile("[\u3002\uff1b\uff0c\uff1a\u201c\u201d\uff08\uff09\u3001\uff1f\u300a\u300b]").MatchString(string(r))) {
			return true
		}
	}
	return false
}

// IsZoneField 检查是否为版本化字段
func IsZoneField(str string) bool {
	return strings.Contains(str, "(") || strings.Contains(str, ")")
}

//开始处理Excel文件
func handleExcelFile(fileName string) {
	fmt.Println()
	fmt.Print("正在处理：" + fileName)
	xlFile, err := xlsx.OpenFile(fileName)
	if err != nil {
		fmt.Printf("%s", err)
	}

	// 这个Excel是否为自述文件（让工具忽略报警）
	isDocFile := false

	// 分析Sheet国际化版本
	sheetMap := linkedhashmap.New()
	for _, sheet := range xlFile.Sheets {
		sheetName := sheet.Name

		// 不是文档时，进行后缀判定
		if !isDocFile && strings.HasSuffix(sheetName, ".doc") {
			isDocFile = true
		}

		// 如果不是Tpl且不是国际化文件
		if !strings.HasSuffix(sheetName, ".tpl") && !strings.HasSuffix(sheetName, ".i18n") {

			// 不是描述文件，且有后续，要警告，基本上就是写错后续的情况
			if !isDocFile && strings.Contains(sheetName, ".") {
				Error("\n\n警告：文件[%s]里发现[%s]异常后缀配表，请火速修正...\n", fileName, sheet.Name)
				if *pauseFlag {
					Error("确认忽略请按回车键继续执行...\n")
					input := bufio.NewScanner(os.Stdin)
					input.Scan()
				}
			}

			continue
		}

		// 发现空格警告提示
		if strings.Contains(sheetName, " ") {
			Error("\n\n警告：文件[%s]里发现[%s]命名中有空格，请火速修正...\n", fileName, sheet.Name)
			if *pauseFlag {
				Error("确认忽略请按回车键继续执行...\n")
				input := bufio.NewScanner(os.Stdin)
				input.Scan()
			}
		}

		// 如果有()，就是国际化Sheet，处理名称进行覆盖之前配置
		if strings.Contains(sheetName, "(") && strings.Contains(sheetName, ")") {
			if *i18nFlag {
				prefix := "(" + *zone + ")"
				if strings.Contains(sheetName, prefix) {
					sheetName = strings.ReplaceAll(sheetName, prefix, "")
				} else {
					continue //不是当前版本，那个配置直接忽略掉
				}
			} else {
				continue //未开始国际化，那个配置直接忽略掉
			}
		}

		// 如果有{}，就是阶服配置
		if strings.Contains(sheetName, "{") && strings.Contains(sheetName, "}") {
			prefix := "{" + strconv.Itoa(*step) + "}"
			if strings.Contains(sheetName, prefix) {
				sheetName = strings.ReplaceAll(sheetName, prefix, "")
			} else {
				continue //不是当前版本，那个配置直接忽略掉
			}
		} else if *step > 1 {
			continue
		}

		if *limit > 1 && strings.HasSuffix(sheetName, ".tpl") && len(sheetName) > 25 {
			Error("\n\n警告：文件[%s]里发现[%s]名称有些长了，请火速修正...\n", fileName, sheet.Name)
			if *pauseFlag {
				Error("确认忽略请按回车键继续执行...\n")
				input := bufio.NewScanner(os.Stdin)
				input.Scan()
			}
		}

		sheetMap.Put(sheetName, sheet)
	}

	//生成文件状态
	genFileFlag := false

	//开始处理Sheet
	sheetMap.Each(func(key, sheet interface{}) {
		if handleExcelSheet(fileName, key.(string), sheet.(*xlsx.Sheet)) {
			genFileFlag = true
		}
	})

	if *step <= 1 && !genFileFlag && !isDocFile {
		//Error("\n这个Excel已没人使用了，请确认是否需要移除.\n")
		//Error(fileName)
		//Error("\n")
	}
}

//开始处理Sheet
func handleExcelSheet(fileName string, sheetName string, sheet *xlsx.Sheet) bool {
	if strings.HasSuffix(sheetName, ".tpl") {
		// 如果Sheet名称是以.tpl结尾则为策划配置模板
		return handleSheet(fileName, sheetName, sheet)
	} else if *i18nFlag && strings.HasSuffix(sheetName, ".i18n") {
		// 如果Sheet名称是以.i18n结尾则为需要翻译的文本
		return handleSheet(fileName, sheetName, sheet)
	}

	//策划用的数据文件？？？
	return true
}

//开始处理策划配置Sheet
func handleSheet(fileName string, sheetName string, sheet *xlsx.Sheet) bool {
	// 如果这个SheetName是$filename.i18n，那要把他换成文件名
	if strings.EqualFold("$filename.i18n", sheetName) {
		// 先把文件名中的路径去了
		_, fileName = filepath.Split(fileName)
		//获取文件名称带后缀
		fileNameWithSuffix := path.Base(fileName)
		//获取文件的后缀(文件类型)
		fileType := path.Ext(fileNameWithSuffix)
		//获取文件名称(不带后缀)
		sheetName = strings.TrimSuffix(fileNameWithSuffix, fileType) + ".i18n"
	}

	fmt.Print(" >> " + sheetName)
	// 查找这个Sheet名称是否存在
	CheckDuplicateSheetName(fileName, sheetName)

	var data [][]string    //CSV的内容
	var titleFlag []bool   //标题是否存在
	var onlyFlag []bool    //唯一判定
	var decimalFlag []bool //允许小数配置标识
	var spacesFlag []bool  //禁止空格

	var titleStringFlag []bool   //这一列是否为字符串
	var titleStringArray []int   //原生下标对应CSV的下标
	var titleListFlag []bool     //这一列是否为字符串
	var noTranslationFlag []bool //这一列是否为字符串

	onlyMap := map[string]int{}
	var zoneDataFlag []bool //版本化字段是否存在
	var zoneIndexData []int //版本化字段所在列编号

	// 申明一个标题最大数量
	var titleMaxNum int = 0
	// 遍历row
	for lineNum, row := range sheet.Rows {
		if row.Hidden {
			Error("\n警告：[%s]里发现隐藏行lineNum=[%d]，请取消隐藏...\n", sheetName, lineNum+1)
		}

		if lineNum == 0 { //标题行
			var lineData []string //用来存这一行的数据
			titleMaxNum = len(row.Cells)
			titleFlag = make([]bool, titleMaxNum)
			titleStringFlag = make([]bool, titleMaxNum)
			titleStringArray = make([]int, titleMaxNum)
			titleListFlag = make([]bool, titleMaxNum)
			noTranslationFlag = make([]bool, titleMaxNum)

			zoneDataFlag = make([]bool, titleMaxNum)
			zoneIndexData = make([]int, titleMaxNum)

			// 遍历cell
			for index, cell := range row.Cells {
				if cell.Hidden {
					Error("\n\n警告：[%s]里发现隐藏列cellNum=[%d]，请取消隐藏...\n", sheetName, index+1)
					if *pauseFlag {
						Error("确认忽略请按回车键继续执行...\n")
						input := bufio.NewScanner(os.Stdin)
						input.Scan()
					}
				}

				v := cell.String()
				if len(v) > 0 {
					// 过滤掉版本化字段
					if !IsZoneField(v) {
						titleFlag[index] = true
						lineData = append(lineData, strings.TrimSpace(v))
					} else if *i18nFlag {
						// 如果开启了国际化，那把此列对应上他所修正的列
						var targetIndex = -1
						for index, cell := range row.Cells {
							subStr := "(" + *zone + ")" + cell.String()
							if strings.EqualFold(subStr, v) {
								targetIndex = index
							}
						}
						if targetIndex >= 0 {
							zoneDataFlag[targetIndex] = true
							zoneIndexData[targetIndex] = index
						}
					}
				}
			}

			// 检测标题行命令是否规范
			CheckNamingSpecification(lineData, sheetName)

			//标题编号不可以重复
			CheckDuplicateTitle(lineData, sheetName)

			data = append(data, lineData)
		} else if lineNum == 1 { // 标题注释行
			onlyFlag = make([]bool, titleMaxNum)
			decimalFlag = make([]bool, titleMaxNum)
			spacesFlag = make([]bool, titleMaxNum)
			for index, cell := range row.Cells {
				if index >= titleMaxNum {
					continue
				}

				cellString := cell.String()
				//不可重复
				if strings.Contains(cellString, FlagOnly) {
					onlyFlag[index] = true
				}
				//小数配置
				if strings.Contains(cellString, DecimalConfig) {
					decimalFlag[index] = true
				}
				//禁止空格
				if strings.Contains(cellString, SpacesConfig) {
					spacesFlag[index] = true
				}
				// 强制字符串
				if strings.Contains(cellString, StringFlag) {
					titleStringFlag[index] = true
				}
				// 强制List
				if strings.Contains(cellString, ListFlag) {
					titleListFlag[index] = true
				}
				// 不要翻译
				if strings.Contains(cellString, NoTranslationFlag) {
					noTranslationFlag[index] = true
				}
			}
		} else if len(row.Cells) == 0 { // 空行
			continue
		} else {
			// 数据注释行
			v := row.Cells[0].String()
			if strings.HasPrefix(v, "//") {
				continue
			}

			hasData := false
			var lineData []string //用来存这一行的数据
			for index, cell := range row.Cells {
				if index < titleMaxNum && titleFlag[index] {
					v := cell.String()

					// 存在国际化列，那要改要配置值
					if zoneDataFlag[index] {
						v = row.Cells[zoneIndexData[index]].String()
					}

					// 判定数据引用错误
					if errTexts.Contains(v) {
						Error("\n\n警告：[%s]里[%d]行中发现错误引用数据[%s]，请火速修正...\n", sheetName, lineNum+1, v)
						if *pauseFlag {
							Error("确认忽略请按回车键继续执行...\n")
							input := bufio.NewScanner(os.Stdin)
							input.Scan()
						}
					}

					//检查小数配置
					if cell.Type() == 2 && !decimalFlag[index] {
						if strings.Contains(v, ".") {
							Error("\n\n%s", fileName)
							Error("\n警告：[%s]里[%d]行中发现小数[%s]，请火速修正...\n", sheetName, lineNum+1, v)
							if *pauseFlag {
								Error("确认忽略请按回车键继续执行...\n")
								input := bufio.NewScanner(os.Stdin)
								input.Scan()
							}
						}
					} else if cell.Type() == 0 && !spacesFlag[index] {
						if strings.Contains(v, " ") {
							Error("\n\n%s", fileName)
							Error("\n警告：[%s]里[%d]行中发现[%s]中有空格，请火速修正...", sheetName, lineNum+1, v)
							Error("\n如果确定需要配置空格，请在第二行注释中添加[空格配置]\n")
							if *pauseFlag {
								Error("确认忽略请按回车键继续执行...\n")
								input := bufio.NewScanner(os.Stdin)
								input.Scan()
							}
						}
					}

					// 国际化文本(这里移除了结束的换行)
					i18nText := v

					// 这个单元格里有不要翻译，那要把这个几个字去掉
					hasNoTranslation := strings.Contains(i18nText, NoTranslationFlag)
					if hasNoTranslation {
						i18nText = strings.ReplaceAll(i18nText, NoTranslationFlag, "")
					}

					if *i18nFlag && IsChineseChar(i18nText) {
						// 需要翻译
						if !(noTranslationFlag[index] || hasNoTranslation) {

							cao := i18nText
							cao = strings.Trim(cao, "\n")
							cao = strings.Trim(cao, " ")
							cao = strings.Trim(cao, "	")

							// 判定是不是已翻译好了
							if text, ok := dict[cao]; ok {
								translatedText.Put(i18nText, text)
								textSourceMap.Put(i18nText, sheet.Name)
								i18nText = strings.ReplaceAll(i18nText, cao, text)
							} else {
								allText.Put(i18nText, "")
							}
						}
					}

					lineData = append(lineData, i18nText)

					// 类型判定
					if titleStringFlag[index] || (!strings.EqualFold("", i18nText) && !IsNum(i18nText)) {
						titleStringFlag[index] = true
					}
					titleStringArray[len(lineData)-1] = index

					if len(i18nText) > 0 {
						hasData = true

						//唯一检查
						if index < len(onlyFlag) && onlyFlag[index] {
							if _, ok := onlyMap[i18nText]; ok {
								Error("\n\n警告：[%s]里发现重复配置[%s]，请火速修正...\n", sheetName, v)
								if *pauseFlag {
									Error("确认忽略请按回车键继续执行...\n")
									input := bufio.NewScanner(os.Stdin)
									input.Scan()
								}
							}
							onlyMap[i18nText] = 1
						}
					}
				}
			}

			if hasData {
				// 由于Excel在读取会忽略后面的空白，所以在转CSV要补上
				totalNum := 0
				for index, v := range titleFlag {
					if v {
						totalNum++

						// 还要忽略掉多出标题的配置
						if totalNum < titleMaxNum && totalNum >= len(lineData) {
							titleStringArray[totalNum-1] = index
						}
					}
				}
				for i := len(lineData); i < totalNum; i++ {
					lineData = append(lineData, "")
				}
				data = append(data, lineData)
			}
		}
	}

	// 如果一列数据都没有，那就不要写文件了
	totalNum := 0
	for _, v := range titleFlag {
		if v {
			totalNum++
		}
	}
	if totalNum == 0 {
		Error("\n\n%s >> %s配置中一列数据都没有吗？？？\n", fileName, sheetName)
		return false
	}

	//生成文件状态
	genFileFlag := false

	if *csvFlag && !ignoreTpl.Contains(sheetName) { // 生成TPL文件
		f, err := os.Create(tplOutputDir + sheetName) //创建文件
		if err != nil {
			panic(err)
		}
		defer f.Close()

		//f.WriteString("\xEF\xBB\xBF") // 写入UTF-8 BOM
		w := csv.NewWriter(f)
		w.Comma = rune(*comma)
		w.WriteAll(data) //写入数据
		w.Flush()
		genFileFlag = true
	}

	// 是否生成JSON
	if *jsonFlag {
		if writerToJsonFile(fileName, sheetName, data, titleStringFlag, titleStringArray, titleListFlag) {
			genFileFlag = true
		}
	}

	return genFileFlag
}

// 生成JSON文件
func writerToJsonFile(fileName string, sheetName string, data [][]string, titleStringFlag []bool, titleStringArray []int, titleListFlag []bool) bool {
	array := strings.SplitN(sheetName, ".", 2)
	jsonFileName := array[0] + ".json"

	// 生成的LUA文件
	f, err := os.Create(tplOutputDir + "/json/" + jsonFileName) //创建文件
	if err != nil {
		panic(err)
	}
	defer f.Close()

	titleLine := data[0]
	list := arraylist.New()
	for i, line := range data {
		if i > 0 {

			m := make(map[string]interface{})

			for index, s := range line {
				title := titleLine[index]
				m[title] = s
			}

			list.Add(m)
		}
	}

	marshal, err := list.ToJSON()
	if err != nil {
		panic(err)
	}
	f.Write(marshal)
	//fmt.Println(string(marshal))
	return true
}

func IsNum(s string) bool {
	// 如果配置中有_就直接否定数字
	if strings.Contains(s, "_") {
		return false
	}
	// 能转化为数字的就算是数字
	_, err := strconv.ParseFloat(s, 64)
	return err == nil
}

// CheckDuplicateSheetName 检测是否存在重复的Sheet名称
func CheckDuplicateSheetName(fileName string, sheetName string) {
	lowerSheetName := strings.ToLower(sheetName)
	if v, ok := allSheet.Get(lowerSheetName); ok {
		Error("\n\n警告：重复文件，请火速修正...\n")
		Error("文件一：[%s]里有[%s]\n", v, sheetName)
		Error("文件二：[%s]里有[%s]\n", fileName, sheetName)
		if *pauseFlag {
			Error("确认忽略请按回车键继续执行...\n")
			input := bufio.NewScanner(os.Stdin)
			input.Scan()
		}
	} else {
		allSheet.Put(lowerSheetName, fileName)
	}
}

// 清理TPL输出目录
func cleanUpDataDir(dirPath string, suffix string) {
	dir, err := ioutil.ReadDir(dirPath)
	if err != nil {
		_ = os.MkdirAll(dirPath, 777) //尝试创建这个目标文件夹（第一次这个是目录是不存在的）
		return
	}

	for _, fi := range dir {
		if fi.IsDir() {
			continue
		}
		if strings.HasSuffix(fi.Name(), suffix) {
			_ = os.Remove(dirPath + fi.Name())
		}
	}

	_ = os.MkdirAll(dirPath, 777) //尝试创建这个目标文件夹（第一次这个是目录是不存在的）
}

// 扫描指定目录下所有Excel文件
// 后缀需要小写方式
func scanningExcelFiles(dirPth string) (files []string, err error) {
	err = filepath.Walk(dirPth, func(filename string, fi os.FileInfo, err error) error { //遍历目录
		// 忽略目录
		if fi.IsDir() {
			return nil
		}

		// 前缀为Excel打开文件也忽略掉
		if strings.HasPrefix(fi.Name(), "~") {
			return nil
		}

		//满足后缀要求的就是Excel
		// 文件名称转化为小写字母
		lowerFilename := strings.ToLower(filename)
		if strings.HasSuffix(lowerFilename, ".xlsx") || strings.HasSuffix(lowerFilename, ".xlsm") || strings.HasSuffix(lowerFilename, ".xls") {
			files = append(files, filename)
		}

		return nil
	})
	return files, err
}
